/* BGP4 SNMP support
   Copyright (C) 1999, 2000 Kunihiro Ishiguro

This file is part of GNU Zebra.

GNU Zebra is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

GNU Zebra is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.

You should have received a copy of the GNU General Public License
along with GNU Zebra; see the file COPYING.  If not, write to the Free
Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
02111-1307, USA.  */

#include <zebra.h>

#ifdef HAVE_SNMP
#ifdef HAVE_NETSNMP
#include <net-snmp/net-snmp-config.h>
#include <net-snmp/net-snmp-includes.h>
#else
#include <asn1.h>
#include <snmp.h>
#include <snmp_impl.h>
#endif

#include "if.h"
#include "log.h"
#include "prefix.h"
#include "command.h"
#include "thread.h"
#include "smux.h"

#include "bgpd/bgpd.h"
#include "bgpd/bgp_table.h"
#include "bgpd/bgp_aspath.h"
#include "bgpd/bgp_attr.h"
#include "bgpd/bgp_route.h"
#include "bgpd/bgp_fsm.h"
#include "bgpd/bgp_snmp.h"

/* BGP4-MIB described in RFC1657. */
#define BGP4MIB 1,3,6,1,2,1,15

/* BGP TRAP. */
#define BGPESTABLISHED			1
#define BGPBACKWARDTRANSITION		2	

/* BGP MIB bgpVersion. */
#define BGPVERSION			      0

/* BGP MIB bgpLocalAs. */
#define BGPLOCALAS			      0

/* BGP MIB bgpPeerTable. */
#define BGPPEERIDENTIFIER                     1
#define BGPPEERSTATE                          2
#define BGPPEERADMINSTATUS                    3
#define BGPPEERNEGOTIATEDVERSION              4
#define BGPPEERLOCALADDR                      5
#define BGPPEERLOCALPORT                      6
#define BGPPEERREMOTEADDR                     7
#define BGPPEERREMOTEPORT                     8
#define BGPPEERREMOTEAS                       9
#define BGPPEERINUPDATES                     10
#define BGPPEEROUTUPDATES                    11
#define BGPPEERINTOTALMESSAGES               12
#define BGPPEEROUTTOTALMESSAGES              13
#define BGPPEERLASTERROR                     14
#define BGPPEERFSMESTABLISHEDTRANSITIONS     15
#define BGPPEERFSMESTABLISHEDTIME            16
#define BGPPEERCONNECTRETRYINTERVAL          17
#define BGPPEERHOLDTIME                      18
#define BGPPEERKEEPALIVE                     19
#define BGPPEERHOLDTIMECONFIGURED            20
#define BGPPEERKEEPALIVECONFIGURED           21
#define BGPPEERMINASORIGINATIONINTERVAL      22
#define BGPPEERMINROUTEADVERTISEMENTINTERVAL 23
#define BGPPEERINUPDATEELAPSEDTIME           24

/* BGP MIB bgpIdentifier. */
#define BGPIDENTIFIER                         0

/* BGP MIB bgpRcvdPathAttrTable */
#define BGPPATHATTRPEER                       1
#define BGPPATHATTRDESTNETWORK                2
#define BGPPATHATTRORIGIN                     3
#define BGPPATHATTRASPATH                     4
#define BGPPATHATTRNEXTHOP                    5
#define BGPPATHATTRINTERASMETRIC              6

/* BGP MIB bgp4PathAttrTable. */
#define BGP4PATHATTRPEER                      1
#define BGP4PATHATTRIPADDRPREFIXLEN           2
#define BGP4PATHATTRIPADDRPREFIX              3
#define BGP4PATHATTRORIGIN                    4
#define BGP4PATHATTRASPATHSEGMENT             5
#define BGP4PATHATTRNEXTHOP                   6
#define BGP4PATHATTRMULTIEXITDISC             7
#define BGP4PATHATTRLOCALPREF                 8
#define BGP4PATHATTRATOMICAGGREGATE           9
#define BGP4PATHATTRAGGREGATORAS             10
#define BGP4PATHATTRAGGREGATORADDR           11
#define BGP4PATHATTRCALCLOCALPREF            12
#define BGP4PATHATTRBEST                     13
#define BGP4PATHATTRUNKNOWN                  14

/* SNMP value hack. */
#define INTEGER ASN_INTEGER
#define INTEGER32 ASN_INTEGER
#define COUNTER32 ASN_COUNTER
#define OCTET_STRING ASN_OCTET_STR
#define IPADDRESS ASN_IPADDRESS
#define GAUGE32 ASN_UNSIGNED

/* Declare static local variables for convenience. */
SNMP_LOCAL_VARIABLES

/* BGP-MIB instances. */
oid bgp_oid [] = { BGP4MIB };

/* IP address 0.0.0.0. */
static struct in_addr bgp_empty_addr = {0};

/* Hook functions. */
static u_char *bgpVersion (struct variable *, oid [], size_t *, int,
			   size_t *, WriteMethod **);
static u_char *bgpLocalAs (struct variable *, oid [], size_t *,
			   int, size_t *, WriteMethod **);
static u_char *bgpPeerTable (struct variable *, oid [], size_t *,
			     int, size_t *, WriteMethod **);
static u_char *bgpRcvdPathAttrTable (struct variable *, oid [], size_t *,
				     int, size_t *, WriteMethod **);
static u_char *bgpIdentifier (struct variable *, oid [], size_t *,
			      int, size_t *, WriteMethod **);
static u_char *bgp4PathAttrTable (struct variable *, oid [], size_t *,
				  int, size_t *, WriteMethod **);
/* static u_char *bgpTraps (); */

struct variable bgp_variables[] = 
{
  /* BGP version. */
  {BGPVERSION,                OCTET_STRING, RONLY, bgpVersion,
   1, {1}},
  /* BGP local AS. */
  {BGPLOCALAS,                INTEGER, RONLY, bgpLocalAs,
   1, {2}},
  /* BGP peer table. */
  {BGPPEERIDENTIFIER,         IPADDRESS, RONLY, bgpPeerTable,
   3, {3, 1, 1}},
  {BGPPEERSTATE,              INTEGER, RONLY, bgpPeerTable,
   3, {3, 1, 2}},
  {BGPPEERADMINSTATUS,        INTEGER, RWRITE, bgpPeerTable,
   3, {3, 1, 3}},
  {BGPPEERNEGOTIATEDVERSION,  INTEGER32, RONLY, bgpPeerTable,
   3, {3, 1, 4}},
  {BGPPEERLOCALADDR,          IPADDRESS, RONLY, bgpPeerTable,
   3, {3, 1, 5}},
  {BGPPEERLOCALPORT,          INTEGER, RONLY, bgpPeerTable,
   3, {3, 1, 6}},
  {BGPPEERREMOTEADDR,         IPADDRESS, RONLY, bgpPeerTable,
   3, {3, 1, 7}},
  {BGPPEERREMOTEPORT,         INTEGER, RONLY, bgpPeerTable,
   3, {3, 1, 8}},
  {BGPPEERREMOTEAS,           INTEGER, RONLY, bgpPeerTable,
   3, {3, 1, 9}},
  {BGPPEERINUPDATES,          COUNTER32, RONLY, bgpPeerTable,
   3, {3, 1, 10}},
  {BGPPEEROUTUPDATES,         COUNTER32, RONLY, bgpPeerTable,
   3, {3, 1, 11}},
  {BGPPEERINTOTALMESSAGES,    COUNTER32, RONLY, bgpPeerTable,
   3, {3, 1, 12}},
  {BGPPEEROUTTOTALMESSAGES,   COUNTER32, RONLY, bgpPeerTable,
   3, {3, 1, 13}},
  {BGPPEERLASTERROR,          OCTET_STRING, RONLY, bgpPeerTable,
   3, {3, 1, 14}},
  {BGPPEERFSMESTABLISHEDTRANSITIONS, COUNTER32, RONLY, bgpPeerTable,
   3, {3, 1, 15}},
  {BGPPEERFSMESTABLISHEDTIME, GAUGE32, RONLY, bgpPeerTable,
   3, {3, 1, 16}},
  {BGPPEERCONNECTRETRYINTERVAL, INTEGER, RWRITE, bgpPeerTable,
   3, {3, 1, 17}},
  {BGPPEERHOLDTIME,           INTEGER, RONLY, bgpPeerTable,
   3, {3, 1, 18}},
  {BGPPEERKEEPALIVE,          INTEGER, RONLY, bgpPeerTable,
   3, {3, 1, 19}},
  {BGPPEERHOLDTIMECONFIGURED, INTEGER, RWRITE, bgpPeerTable,
   3, {3, 1, 20}},
  {BGPPEERKEEPALIVECONFIGURED, INTEGER, RWRITE, bgpPeerTable,
   3, {3, 1, 21}},
  {BGPPEERMINASORIGINATIONINTERVAL, INTEGER, RWRITE, bgpPeerTable,
   3, {3, 1, 22}},
  {BGPPEERMINROUTEADVERTISEMENTINTERVAL, INTEGER, RWRITE, bgpPeerTable,
   3, {3, 1, 23}},
  {BGPPEERINUPDATEELAPSEDTIME, GAUGE32, RONLY, bgpPeerTable,
   3, {3, 1, 24}},
  /* BGP identifier. */
  {BGPIDENTIFIER,             IPADDRESS, RONLY, bgpIdentifier,
   1, {4}},
  /* BGP received path attribute table. */
  {BGPPATHATTRPEER,           IPADDRESS, RONLY, bgpRcvdPathAttrTable,
   3, {5, 1, 1}},
  {BGPPATHATTRDESTNETWORK,    IPADDRESS, RONLY, bgpRcvdPathAttrTable,
   3, {5, 1, 2}},
  {BGPPATHATTRORIGIN,         INTEGER, RONLY, bgpRcvdPathAttrTable,
   3, {5, 1, 3}},
  {BGPPATHATTRASPATH,         OCTET_STRING, RONLY, bgpRcvdPathAttrTable,
   3, {5, 1, 4}},
  {BGPPATHATTRNEXTHOP,        IPADDRESS, RONLY, bgpRcvdPathAttrTable,
   3, {5, 1, 5}},
  {BGPPATHATTRINTERASMETRIC,  INTEGER32, RONLY, bgpRcvdPathAttrTable,
   3, {5, 1, 6}},
  /* BGP-4 received path attribute table. */
  {BGP4PATHATTRPEER, IPADDRESS, RONLY, bgp4PathAttrTable,
   3, {6, 1, 1}},
  {BGP4PATHATTRIPADDRPREFIXLEN, INTEGER, RONLY, bgp4PathAttrTable,
   3, {6, 1, 2}},
  {BGP4PATHATTRIPADDRPREFIX,  IPADDRESS, RONLY, bgp4PathAttrTable,
   3, {6, 1, 3}},
  {BGP4PATHATTRORIGIN,        INTEGER, RONLY, bgp4PathAttrTable,
   3, {6, 1, 4}},
  {BGP4PATHATTRASPATHSEGMENT, OCTET_STRING, RONLY, bgp4PathAttrTable,
   3, {6, 1, 5}},
  {BGP4PATHATTRNEXTHOP,       IPADDRESS, RONLY, bgp4PathAttrTable,
   3, {6, 1, 6}},
  {BGP4PATHATTRMULTIEXITDISC, INTEGER, RONLY, bgp4PathAttrTable,
   3, {6, 1, 7}},
  {BGP4PATHATTRLOCALPREF,     INTEGER, RONLY, bgp4PathAttrTable,
   3, {6, 1, 8}},
  {BGP4PATHATTRATOMICAGGREGATE, INTEGER, RONLY, bgp4PathAttrTable,
   3, {6, 1, 9}},
  {BGP4PATHATTRAGGREGATORAS,  INTEGER, RONLY, bgp4PathAttrTable,
   3, {6, 1, 10}},
  {BGP4PATHATTRAGGREGATORADDR, IPADDRESS, RONLY, bgp4PathAttrTable,
   3, {6, 1, 11}},
  {BGP4PATHATTRCALCLOCALPREF, INTEGER, RONLY, bgp4PathAttrTable,
   3, {6, 1, 12}},
  {BGP4PATHATTRBEST,          INTEGER, RONLY, bgp4PathAttrTable,
   3, {6, 1, 13}},
  {BGP4PATHATTRUNKNOWN,       OCTET_STRING, RONLY, bgp4PathAttrTable,
   3, {6, 1, 14}},
};


static u_char *
bgpVersion (struct variable *v, oid name[], size_t *length, int exact,
	    size_t *var_len, WriteMethod **write_method)
{
  static u_char version;

  if (smux_header_generic(v, name, length, exact, var_len, write_method)
      == MATCH_FAILED)
    return NULL;

  /* Retrun BGP version.  Zebra bgpd only support version 4. */
  version = (0x80 >> (BGP_VERSION_4 - 1));

  /* Return octet string length 1. */
  *var_len = 1;
  return (u_char *)&version;
}

static u_char *
bgpLocalAs (struct variable *v, oid name[], size_t *length,
	    int exact, size_t *var_len, WriteMethod **write_method)
{
  struct bgp *bgp;

  if (smux_header_generic(v, name, length, exact, var_len, write_method)
      == MATCH_FAILED)
    return NULL;

  /* Get BGP structure. */
  bgp = bgp_get_default ();
  if (! bgp)
    return NULL;

  return SNMP_INTEGER (bgp->as);
}

static struct peer *
peer_lookup_addr_ipv4 (struct in_addr *src)
{
  struct bgp *bgp;
  struct peer *peer;
  struct listnode *node;
  struct in_addr addr;
  int ret;

  bgp = bgp_get_default ();
  if (! bgp)
    return NULL;

  for (ALL_LIST_ELEMENTS_RO (bgp->peer, node, peer))
    {
      ret = inet_pton (AF_INET, peer->host, &addr);
      if (ret > 0)
	{
	  if (IPV4_ADDR_SAME (&addr, src))
	    return peer;
	}
    }
  return NULL;
}

static struct peer *
bgp_peer_lookup_next (struct in_addr *src)
{
  struct bgp *bgp;
  struct peer *peer;
  struct listnode *node;
  struct in_addr *p;
  union sockunion su;
  int ret;

  memset (&su, 0, sizeof (union sockunion));

  bgp = bgp_get_default ();
  if (! bgp)
    return NULL;

  for (ALL_LIST_ELEMENTS_RO (bgp->peer, node, peer))
    {
      ret = inet_pton (AF_INET, peer->host, &su.sin.sin_addr);
      if (ret > 0)
	{
	  p = &su.sin.sin_addr;

	  if (ntohl (p->s_addr) > ntohl (src->s_addr))
	    {
	      src->s_addr = p->s_addr;
	      return peer;
	    }
	}
    }
  return NULL;
}

static struct peer *
bgpPeerTable_lookup (struct variable *v, oid name[], size_t *length, 
		     struct in_addr *addr, int exact)
{
  struct peer *peer = NULL;
  int len;

  if (exact)
    {
      /* Check the length. */
      if (*length - v->namelen != sizeof (struct in_addr))
	return NULL;

      oid2in_addr (name + v->namelen, IN_ADDR_SIZE, addr);

      peer = peer_lookup_addr_ipv4 (addr);
      return peer;
    }
  else
    {
      len = *length - v->namelen;
      if (len > 4) len = 4;
      
      oid2in_addr (name + v->namelen, len, addr);
      
      peer = bgp_peer_lookup_next (addr);

      if (peer == NULL)
	return NULL;

      oid_copy_addr (name + v->namelen, addr, sizeof (struct in_addr));
      *length = sizeof (struct in_addr) + v->namelen;

      return peer;
    }
  return NULL;
}

/* BGP write methods. */
static int
write_bgpPeerTable (int action, u_char *var_val,
		    u_char var_val_type, size_t var_val_len,
		    u_char *statP, oid *name, size_t length,
		    struct variable *v)
{
  struct in_addr addr;
  struct peer *peer;
  long intval;
  size_t bigsize = SNMP_MAX_LEN;
  
  if (var_val_type != ASN_INTEGER) 
    {
      return SNMP_ERR_WRONGTYPE;
    }
  if (var_val_len != sizeof (long)) 
    {
      return SNMP_ERR_WRONGLENGTH;
    }

  if (! asn_parse_int(var_val, &bigsize, &var_val_type,
                      &intval, sizeof(long)))
    {
      return SNMP_ERR_WRONGENCODING;
    }

  memset (&addr, 0, sizeof (struct in_addr));

  peer = bgpPeerTable_lookup (v, name, &length, &addr, 1);
  if (! peer)
    return SNMP_ERR_NOSUCHNAME;

  printf ("val: %ld\n", intval);

  switch (v->magic)
    {
    case BGPPEERADMINSTATUS:
#define BGP_PeerAdmin_stop  1
#define BGP_PeerAdmin_start 2
      /* When the peer is established,   */
      if (intval == BGP_PeerAdmin_stop)
	BGP_EVENT_ADD (peer, BGP_Stop);
      else if (intval == BGP_PeerAdmin_start)
	;			/* Do nothing. */
      else
	return SNMP_ERR_NOSUCHNAME;
      break;
    case BGPPEERCONNECTRETRYINTERVAL:
      SET_FLAG (peer->config, PEER_CONFIG_CONNECT);
      peer->connect = intval;
      peer->v_connect = intval;
      break;
    case BGPPEERHOLDTIMECONFIGURED:
      SET_FLAG (peer->config, PEER_CONFIG_TIMER);
      peer->holdtime = intval;
      peer->v_holdtime = intval;
      break;
    case BGPPEERKEEPALIVECONFIGURED:
      SET_FLAG (peer->config, PEER_CONFIG_TIMER);
      peer->keepalive = intval;
      peer->v_keepalive = intval;
      break;
    case BGPPEERMINASORIGINATIONINTERVAL:
      peer->v_asorig = intval;
      break;
    case BGPPEERMINROUTEADVERTISEMENTINTERVAL:
      peer->v_routeadv = intval;
      break;
    }
  return SNMP_ERR_NOERROR;
}

static u_char *
bgpPeerTable (struct variable *v, oid name[], size_t *length,
	      int exact, size_t *var_len, WriteMethod **write_method)
{
  static struct in_addr addr;
  struct peer *peer;

  *write_method = NULL;
  memset (&addr, 0, sizeof (struct in_addr));

  peer = bgpPeerTable_lookup (v, name, length, &addr, exact);
  if (! peer)
    return NULL;

  switch (v->magic)
    {
    case BGPPEERIDENTIFIER:
      return SNMP_IPADDRESS (peer->remote_id);
      break;
    case BGPPEERSTATE:
      return SNMP_INTEGER (peer->status);
      break;
    case BGPPEERADMINSTATUS:
      *write_method = write_bgpPeerTable;
#define BGP_PeerAdmin_stop  1
#define BGP_PeerAdmin_start 2
      if (CHECK_FLAG (peer->flags, PEER_FLAG_SHUTDOWN))
	return SNMP_INTEGER (BGP_PeerAdmin_stop);
      else
	return SNMP_INTEGER (BGP_PeerAdmin_start);
      break;
    case BGPPEERNEGOTIATEDVERSION:
      return SNMP_INTEGER (BGP_VERSION_4);
      break;
    case BGPPEERLOCALADDR:
      if (peer->su_local)
	return SNMP_IPADDRESS (peer->su_local->sin.sin_addr);
      else
	return SNMP_IPADDRESS (bgp_empty_addr);
      break;
    case BGPPEERLOCALPORT:
      if (peer->su_local)
	return SNMP_INTEGER (ntohs (peer->su_local->sin.sin_port));
      else
	return SNMP_INTEGER (0);
      break;
    case BGPPEERREMOTEADDR:
      if (peer->su_remote)
	return SNMP_IPADDRESS (peer->su_remote->sin.sin_addr);
      else
	return SNMP_IPADDRESS (bgp_empty_addr);
      break;
    case BGPPEERREMOTEPORT:
      if (peer->su_remote)
	return SNMP_INTEGER (ntohs (peer->su_remote->sin.sin_port));
      else
	return SNMP_INTEGER (0);
      break;
    case BGPPEERREMOTEAS:
      return SNMP_INTEGER (peer->as);
      break;
    case BGPPEERINUPDATES:
      return SNMP_INTEGER (peer->update_in);
      break;
    case BGPPEEROUTUPDATES:
      return SNMP_INTEGER (peer->update_out);
      break;
    case BGPPEERINTOTALMESSAGES:
      return SNMP_INTEGER (peer->open_in + peer->update_in
			   + peer->keepalive_in + peer->notify_in
			   + peer->refresh_in + peer->dynamic_cap_in);
      break;
    case BGPPEEROUTTOTALMESSAGES:
      return SNMP_INTEGER (peer->open_out + peer->update_out
			   + peer->keepalive_out + peer->notify_out
			   + peer->refresh_out + peer->dynamic_cap_out);
      break;
    case BGPPEERLASTERROR:
      {
	static u_char lasterror[2];
	lasterror[0] = peer->notify.code;
	lasterror[1] = peer->notify.subcode;
	*var_len = 2;
	return (u_char *)&lasterror;
      }
      break;
    case BGPPEERFSMESTABLISHEDTRANSITIONS:
      return SNMP_INTEGER (peer->established);
      break;
    case BGPPEERFSMESTABLISHEDTIME:
      if (peer->uptime == 0)
	return SNMP_INTEGER (0);
      else
	return SNMP_INTEGER (bgp_clock () - peer->uptime);
      break;
    case BGPPEERCONNECTRETRYINTERVAL:
      *write_method = write_bgpPeerTable;
      return SNMP_INTEGER (peer->v_connect);
      break;
    case BGPPEERHOLDTIME:
      return SNMP_INTEGER (peer->v_holdtime);
      break;
    case BGPPEERKEEPALIVE:
      return SNMP_INTEGER (peer->v_keepalive);
      break;
    case BGPPEERHOLDTIMECONFIGURED:
      *write_method = write_bgpPeerTable;
      if (CHECK_FLAG (peer->config, PEER_CONFIG_TIMER))
	return SNMP_INTEGER (peer->holdtime);
      else
	return SNMP_INTEGER (peer->v_holdtime);
      break;
    case BGPPEERKEEPALIVECONFIGURED:
      *write_method = write_bgpPeerTable;
      if (CHECK_FLAG (peer->config, PEER_CONFIG_TIMER))
	return SNMP_INTEGER (peer->keepalive);
      else
	return SNMP_INTEGER (peer->v_keepalive);
      break;
    case BGPPEERMINASORIGINATIONINTERVAL:
      *write_method = write_bgpPeerTable;
      return SNMP_INTEGER (peer->v_asorig);
      break;
    case BGPPEERMINROUTEADVERTISEMENTINTERVAL:
      *write_method = write_bgpPeerTable;
      return SNMP_INTEGER (peer->v_routeadv);
      break;
    case BGPPEERINUPDATEELAPSEDTIME:
      if (peer->update_time == 0)
	return SNMP_INTEGER (0);
      else
	return SNMP_INTEGER (bgp_clock () - peer->update_time);
      break;
    default:
      return NULL;
      break;
    }  
  return NULL;
}

static u_char *
bgpIdentifier (struct variable *v, oid name[], size_t *length,
	       int exact, size_t *var_len, WriteMethod **write_method)
{
  struct bgp *bgp;

  if (smux_header_generic(v, name, length, exact, var_len, write_method)
      == MATCH_FAILED)
    return NULL;

  bgp = bgp_get_default ();
  if (!bgp)
    return NULL;

  return SNMP_IPADDRESS (bgp->router_id);
}

static u_char *
bgpRcvdPathAttrTable (struct variable *v, oid name[], size_t *length,
		      int exact, size_t *var_len, WriteMethod **write_method)
{
  /* Received Path Attribute Table.  This table contains, one entry
     per path to a network, path attributes received from all peers
     running BGP version 3 or less.  This table is obsolete, having
     been replaced in functionality with the bgp4PathAttrTable.  */
  return NULL;
}

static struct bgp_info *
bgp4PathAttrLookup (struct variable *v, oid name[], size_t *length,
		    struct bgp *bgp, struct prefix_ipv4 *addr, int exact)
{
  oid *offset;
  int offsetlen;
  struct bgp_info *binfo;
  struct bgp_info *min;
  struct bgp_node *rn;
  union sockunion su;
  unsigned int len;
  struct in_addr paddr;

#define BGP_PATHATTR_ENTRY_OFFSET \
          (IN_ADDR_SIZE + 1 + IN_ADDR_SIZE)

  if (exact)
    {
      if (*length - v->namelen != BGP_PATHATTR_ENTRY_OFFSET)
	return NULL;

      /* Set OID offset for prefix. */
      offset = name + v->namelen;
      oid2in_addr (offset, IN_ADDR_SIZE, &addr->prefix);
      offset += IN_ADDR_SIZE;

      /* Prefix length. */
      addr->prefixlen = *offset;
      offset++;

      /* Peer address. */
      su.sin.sin_family = AF_INET;
      oid2in_addr (offset, IN_ADDR_SIZE, &su.sin.sin_addr);

      /* Lookup node. */
      rn = bgp_node_lookup (bgp->rib[AFI_IP][SAFI_UNICAST], 
			      (struct prefix *) addr);
      if (rn)
	{
	  bgp_unlock_node (rn);

	  for (binfo = rn->info; binfo; binfo = binfo->next)
	    if (sockunion_same (&binfo->peer->su, &su))
	      return binfo;
	}
    }
  else
    {
      offset = name + v->namelen;
      offsetlen = *length - v->namelen;
      len = offsetlen;

      if (offsetlen == 0)
	rn = bgp_table_top (bgp->rib[AFI_IP][SAFI_UNICAST]);
      else
	{
	  if (len > IN_ADDR_SIZE)
	    len = IN_ADDR_SIZE;
      
	  oid2in_addr (offset, len, &addr->prefix);

	  offset += IN_ADDR_SIZE;
	  offsetlen -= IN_ADDR_SIZE;

	  if (offsetlen > 0)
	    addr->prefixlen = *offset;
	  else
	    addr->prefixlen = len * 8;

	  rn = bgp_node_get (bgp->rib[AFI_IP][SAFI_UNICAST],
			       (struct prefix *) addr);

	  offset++;
	  offsetlen--;
	}

      if (offsetlen > 0)
	{
	  len = offsetlen;
	  if (len > IN_ADDR_SIZE)
	    len = IN_ADDR_SIZE;

	  oid2in_addr (offset, len, &paddr);
	}
      else
	paddr.s_addr = 0;

      if (! rn)
	return NULL;

      do
	{
	  min = NULL;

	  for (binfo = rn->info; binfo; binfo = binfo->next)
	    {
	      if (binfo->peer->su.sin.sin_family == AF_INET
		  && ntohl (paddr.s_addr) 
		  < ntohl (binfo->peer->su.sin.sin_addr.s_addr))
		{
		  if (min)
		    {
		      if (ntohl (binfo->peer->su.sin.sin_addr.s_addr) 
			  < ntohl (min->peer->su.sin.sin_addr.s_addr))
			min = binfo;
		    }
		  else
		    min = binfo;
		}
	    }

	  if (min)
	    {
	      *length = v->namelen + BGP_PATHATTR_ENTRY_OFFSET;

	      offset = name + v->namelen;
	      oid_copy_addr (offset, &rn->p.u.prefix4, IN_ADDR_SIZE);
	      offset += IN_ADDR_SIZE;
	      *offset = rn->p.prefixlen;
	      offset++;
	      oid_copy_addr (offset, &min->peer->su.sin.sin_addr, 
			     IN_ADDR_SIZE);
	      addr->prefix = rn->p.u.prefix4;
	      addr->prefixlen = rn->p.prefixlen;

	      bgp_unlock_node (rn);

	      return min;
	    }

	  paddr.s_addr = 0;
	}
      while ((rn = bgp_route_next (rn)) != NULL);
    }
  return NULL;
}

static u_char *
bgp4PathAttrTable (struct variable *v, oid name[], size_t *length,
		   int exact, size_t *var_len, WriteMethod **write_method)
{
  struct bgp *bgp;
  struct bgp_info *binfo;
  struct prefix_ipv4 addr;
  
  bgp = bgp_get_default ();
  if (! bgp)
    return NULL;

  memset (&addr, 0, sizeof (struct prefix_ipv4));

  binfo = bgp4PathAttrLookup (v, name, length, bgp, &addr, exact);
  if (! binfo)
    return NULL;

  switch (v->magic)
    {
    case BGP4PATHATTRPEER:	/* 1 */
      return SNMP_IPADDRESS (binfo->peer->su.sin.sin_addr);
      break;
    case BGP4PATHATTRIPADDRPREFIXLEN: /* 2 */
      return SNMP_INTEGER (addr.prefixlen);
      break;
    case BGP4PATHATTRIPADDRPREFIX: /* 3 */
      return SNMP_IPADDRESS (addr.prefix);
      break;
    case BGP4PATHATTRORIGIN:	/* 4 */
      return SNMP_INTEGER (binfo->attr->origin);
      break;
    case BGP4PATHATTRASPATHSEGMENT: /* 5 */
      return aspath_snmp_pathseg (binfo->attr->aspath, var_len);
      break;
    case BGP4PATHATTRNEXTHOP:	/* 6 */
      return SNMP_IPADDRESS (binfo->attr->nexthop);
      break;
    case BGP4PATHATTRMULTIEXITDISC: /* 7 */
      return SNMP_INTEGER (binfo->attr->med);
      break;
    case BGP4PATHATTRLOCALPREF:	/* 8 */
      return SNMP_INTEGER (binfo->attr->local_pref);
      break;
    case BGP4PATHATTRATOMICAGGREGATE: /* 9 */
      return SNMP_INTEGER (1);
      break;
    case BGP4PATHATTRAGGREGATORAS: /* 10 */
      if (binfo->attr->extra)
        return SNMP_INTEGER (binfo->attr->extra->aggregator_as);
      else
        return SNMP_INTEGER (0);
      break;
    case BGP4PATHATTRAGGREGATORADDR: /* 11 */
      if (binfo->attr->extra)
        return SNMP_IPADDRESS (binfo->attr->extra->aggregator_addr);
      else
        return SNMP_INTEGER (0);
      break;
    case BGP4PATHATTRCALCLOCALPREF: /* 12 */
      return SNMP_INTEGER (-1);
      break;
    case BGP4PATHATTRBEST:	/* 13 */
#define BGP4_PathAttrBest_false 1
#define BGP4_PathAttrBest_true  2
      if (CHECK_FLAG (binfo->flags, BGP_INFO_SELECTED))
	return SNMP_INTEGER (BGP4_PathAttrBest_true);
      else
	return SNMP_INTEGER (BGP4_PathAttrBest_false);
      break;
    case BGP4PATHATTRUNKNOWN:	/* 14 */
      *var_len = 0;
      return NULL;
      break;
    }
  return NULL;
}

/* BGP Traps. */
struct trap_object bgpTrapList[] =
{
  {bgpPeerTable, 3, {3, 1, BGPPEERLASTERROR}},
  {bgpPeerTable, 3, {3, 1, BGPPEERSTATE}}
};

void
bgpTrapEstablished (struct peer *peer)
{
  int ret;
  struct in_addr addr;
  oid index[sizeof (oid) * IN_ADDR_SIZE];

  ret = inet_aton (peer->host, &addr);
  if (ret == 0)
    return;

  oid_copy_addr (index, &addr, IN_ADDR_SIZE);

  smux_trap (bgp_oid, sizeof bgp_oid / sizeof (oid),
	     index, IN_ADDR_SIZE,
	     bgpTrapList, sizeof bgpTrapList / sizeof (struct trap_object),
	     bm->start_time - bgp_clock (), BGPESTABLISHED);
}

void
bgpTrapBackwardTransition (struct peer *peer)
{
  int ret;
  struct in_addr addr;
  oid index[sizeof (oid) * IN_ADDR_SIZE];

  ret = inet_aton (peer->host, &addr);
  if (ret == 0)
    return;

  oid_copy_addr (index, &addr, IN_ADDR_SIZE);

  smux_trap (bgp_oid, sizeof bgp_oid / sizeof (oid),
	     index, IN_ADDR_SIZE,
	     bgpTrapList, sizeof bgpTrapList / sizeof (struct trap_object),
	     bm->start_time - bgp_clock (), BGPBACKWARDTRANSITION);
}

void
bgp_snmp_init (void)
{
  smux_init (bm->master);
  REGISTER_MIB("mibII/bgp", bgp_variables, variable, bgp_oid);
}
#endif /* HAVE_SNMP */
